"""搭建demo框架"""

import os
import csv
from collections import namedtuple
from case_convert import kebab_case, pascal_case, snake_case

Demo = namedtuple("Demo", "cat,comp,comp_name,demo_name,demo")


class CompItem:
    """组件"""

    def __init__(self, comp, comp_name=None, cat=None):
        if cat is None:
            cat = "全局"
        if comp_name is None:
            comp_name = comp
        self.comp = comp
        self.comp_name = comp_name
        self.cat = cat

    def __eq__(self, __value: object) -> bool:
        """判断是否相等

        >>> CompItem("button") == CompItem("Button")
        True
        """
        if not isinstance(__value, CompItem):
            return False
        return self.nz_kebab == __value.nz_kebab

    def __repr__(self):
        return f"CompItem(comp='{self.comp}', comp_name='{self.comp_name}', cat='{self.cat}')"

    @property
    def nz_kebab(self):
        """计算组件路径

        >>> CompItem("Button").nz_kebab
        'button'

        >>> CompItem("BackTop").nz_kebab
        'back-top'

        >>> CompItem("QRCode").nz_kebab
        'qr-code'

        >>> CompItem("Autocomplete").nz_kebab
        'auto-complete'
        """
        comp = self.comp
        if comp == "QRCode":
            return "qr-code"
        if comp == "Autocomplete":
            return "auto-complete"
        return kebab_case(comp)

    @property
    def nz_pascal(self):
        """取得组件模块名

        >>> CompItem("Button").nz_pascal
        'Button'

        >>> CompItem("Breadcrumb").nz_pascal
        'BreadCrumb'

        >>> CompItem("Dropdown").nz_pascal
        'DropDown'

        >>> CompItem("QRCode").nz_pascal
        'QRCode'

        >>> CompItem("Tooltip").nz_pascal
        'ToolTip'
        """
        name = self.comp
        if name == "Breadcrumb":
            return "BreadCrumb"
        if name == "Dropdown":
            return "DropDown"
        if name == "QRCode":
            return "QRCode"
        if name == "Tooltip":
            return "ToolTip"
        return pascal_case(name)

    @property
    def nz_module(self):
        """取得组件模块名

        >>> CompItem("Button").nz_module
        'NzButtonModule'
        """
        pascal = self.nz_pascal
        return f"Nz{pascal}Module"

    @property
    def nz_import(self):
        """计算组件导入语句

        >>> CompItem("Button").nz_import
        "import { NzButtonModule } from 'ng-zorro-antd/button';"
        """
        nz_comp_path = self.nz_kebab
        nz_module_name = self.nz_module
        return f"import {{ {nz_module_name} }} from 'ng-zorro-antd/{nz_comp_path}';"

    @property
    def demo_pascal(self):
        """计算demo的组件名称

        >>> CompItem("BackTop").demo_pascal
        'BackTop'

        >>> CompItem("QRCode").demo_pascal
        'QrCode'
        """
        kebab = self.nz_kebab
        return pascal_case(kebab)


class DemoItem:
    """Demo项目"""

    def __init__(self, demo, comp, comp_name=None, demo_name=None, cat=None) -> None:
        if cat is None:
            cat = "全局"
        if comp_name is None:
            comp_name = comp
        if demo_name is None:
            demo_name = demo
        self.comp = CompItem(cat=cat, comp=comp, comp_name=comp_name)
        self.demo_name = demo_name
        self.demo = demo

    def __repr__(self) -> str:
        return f"DemoItem(demo='{self.demo}', comp='{self.comp.comp}', comp_name='{self.comp.comp_name}', demo_name='{self.demo_name}', cat='{self.cat}')"

    def __str__(self) -> str:
        return f"{self.cat} / {self.comp.comp}({self.comp.comp_name}) / {self.demo}({self.demo_name})"

    @property
    def cat(self):
        """取得类别"""
        return self.comp.cat

    @property
    def component_pascal(self):
        """组名pascal名
        >>> DemoItem("basic", "QRCode").component_pascal
        'QrCode'
        """
        return self.comp.demo_pascal

    @property
    def component_kebab(self):
        """组名kebab名
        >>> DemoItem("basic", "QRCode").component_kebab
        'qr-code'
        """
        return self.comp.nz_kebab

    @property
    def pascal(self):
        """pascal名
        >>> DemoItem("basic", "QRCode").pascal
        'Basic'
        """
        return pascal_case(self.demo)

    @property
    def kebab(self):
        """kebab名
        >>> DemoItem("basic-group", "QRCode").kebab
        'basic-group'
        """
        return kebab_case(self.demo)

    @property
    def demo_pascal(self):
        """pascal demo 名
        >>> DemoItem("basic-group", "QRCode").demo_pascal
        'QrCodeBasicGroupDemo'
        """
        return pascal_case(self.demo_kebab)

    @property
    def demo_kebab(self):
        """kebab demo名
        >>> DemoItem("basic-group", "QRCode").demo_kebab
        'qr-code-basic-group-demo'
        """
        return kebab_case(self.component_kebab + "-" + self.kebab + "-demo")

    @property
    def demo_comp(self):
        """取得demo的组件名

        >>> DemoItem("basic", "QRCode").demo_comp
        'QrCodeBasicDemoComponent'
        """
        return f"{self.demo_pascal}Component"

    @property
    def route_path(self):
        """取得路由路径

        >>> DemoItem("basic", "QRCode").route_path
        'qr-code-basic'
        """
        return kebab_case(self.component_kebab + "-" + self.kebab)

    @property
    def file_path(self):
        """取得demo的文件路径

        >>> DemoItem("basic", "QRCode").file_path
        'src/app/qr-code/qr-code-basic-demo/qr-code-basic-demo.component.ts'
        """
        comp_kebab = self.component_kebab
        demo_kebab = self.demo_kebab
        return f"src/app/{comp_kebab}/{demo_kebab}/{demo_kebab}.component.ts"

    @property
    def ng_comp(self):
        """取得创建组件的指令
        >>> DemoItem("basic", "QRCode").ng_comp
        'ng g c qr-code/qr-code-basic-demo --module=app'
        """
        comp = self.component_kebab
        demo = self.demo_kebab
        return f"ng g c {comp}/{demo} --module=app"

    @property
    def ng_file_path(self):
        """文件路径
        >>> DemoItem('basic', "Button").ng_file_path
        'src/app/button/button-basic-demo/button-basic-demo.component.ts'
        """

        comp = self.component_kebab
        demo = self.demo_kebab
        return f"src/app/{comp}/{demo}/{demo}.component.ts"

    @property
    def ng_component_path(self):
        """组件路径
        >>> DemoItem('basic', "Button").ng_component_path
        './button/button-basic-demo/button-basic-demo.component'
        """

        comp = self.component_kebab
        demo = self.demo_kebab
        return f"./{comp}/{demo}/{demo}.component"

    def generate_ng_comp(self):
        """创建组件"""
        if not os.path.exists(self.ng_file_path):
            os.system(self.ng_comp)

    @property
    def demo_import(self):
        """取得demo组件import语句

        >>> DemoItem("basic", "QRCode").demo_import
        "import { QrCodeBasicDemoComponent } from './qr-code/qr-code-basic-demo/qr-code-basic-demo.component';"
        """
        return f"import {{ {self.demo_comp} }} from '{self.ng_component_path}';"

    @property
    def route(self):
        """添加路径语句

        >>> DemoItem("basic", "QRCode").route
        "  { path: 'qr-code-basic', component: QrCodeBasicDemoComponent },"
        """
        path = self.route_path
        comp = self.demo_comp
        return "  { path: '%s', component: %s }," % (path, comp)


class DemoList:
    """demo列表"""

    def __init__(self, file=None) -> None:
        if file is None:
            file = r"doc\demos.csv"

        with open(file, mode="r", encoding="utf-8") as fin:
            lines = fin.readlines()
        lines = [line for line in lines if line.strip()]  # 去掉空行
        rows = list(csv.reader(lines))  # 把每行转为元组
        demos = [Demo(*row) for row in rows]
        self.demos = [DemoItem(**(demo._asdict())) for demo in demos]

    def find_demo(self, target: str):
        """取得demo
        >>> DemoList().find_demo("components-badge-demo-basic")
        DemoItem(demo='basic', comp='Badge', comp_name='徽标数', demo_name='基本', cat='数据展示')

        >>> DemoList().find_demo("badge-demo-basic")
        DemoItem(demo='basic', comp='Badge', comp_name='徽标数', demo_name='基本', cat='数据展示')

        >>> DemoList().find_demo("components-qr-code-demo-basic")
        DemoItem(demo='basic', comp='QRCode', comp_name='二维码', demo_name='基本', cat='数据展示')

        >>> DemoList().find_demo("components-drawer-demo-basic-right")
        DemoItem(demo='basic_right', comp='Drawer', comp_name='抽屉', demo_name='基础抽屉', cat='反馈')
        """
        if target.startswith("components-"):
            prefix_len = len("components-")
            target = target[prefix_len:]
        comp_demo = target.split("-demo-")
        if len(comp_demo) != 2:
            raise ValueError("target 错误")
        comp = pascal_case(comp_demo[0])
        demo_name = snake_case(comp_demo[1])
        for demo in self.demos:
            if demo.comp.comp.lower() == comp.lower() and demo.demo == demo_name:
                return demo

    def iter_category(self):
        """遍历类别
        >>> list(DemoList().iter_category())
        ['通用', '布局', '导航', '数据录入', '数据展示', '反馈', '其他', '特色组件']
        """
        categories = set()
        for demo in self.demos:
            category = demo.cat
            if category not in categories:
                yield category
                categories.add(category)

    def iter_component(self, cat):
        """遍历组件
        >>> list(DemoList().iter_component('通用'))
        [CompItem(comp='Button', comp_name='按钮', cat='通用'), CompItem(comp='Icon', comp_name='图标', cat='通用'), CompItem(comp='Typography', comp_name='排版', cat='通用')]
        """
        components = set()
        for demo in self.demos:
            if demo.cat != cat:
                continue
            comp = demo.comp
            if comp.comp not in components:
                yield comp
                components.add(comp.comp)

    def iter_all_component(self):
        """遍历所有组件"""
        for cat in self.iter_category():
            yield from self.iter_component(cat)

    def find_component(self, comp_name):
        """查找组件
        >>> DemoList().find_component('avatar')
        CompItem(comp='Avatar', comp_name='头像', cat='数据展示')
        """
        for comp in self.iter_all_component():
            if comp.comp.lower() == comp_name.lower():
                return comp

    def iter_demo(self, comp):
        """遍历组件
        >>> list(DemoList().iter_demo('Button'))
        [DemoItem(demo='basic', comp='Button', comp_name='按钮', demo_name='按钮类型', cat='通用'), DemoItem(demo='icon', comp='Button', comp_name='按钮', demo_name='图标按钮', cat='通用'), DemoItem(demo='size', comp='Button', comp_name='按钮', demo_name='按钮尺寸', cat='通用'), DemoItem(demo='disabled', comp='Button', comp_name='按钮', demo_name='不可用状态', cat='通用'), DemoItem(demo='loading', comp='Button', comp_name='按钮', demo_name='加载中状态', cat='通用'), DemoItem(demo='multiple', comp='Button', comp_name='按钮', demo_name='多个按钮组合', cat='通用'), DemoItem(demo='button_group', comp='Button', comp_name='按钮', demo_name='按钮组合', cat='通用'), DemoItem(demo='ghost', comp='Button', comp_name='按钮', demo_name='幽灵按钮', cat='通用'), DemoItem(demo='block', comp='Button', comp_name='按钮', demo_name='Block 按钮', cat='通用'), DemoItem(demo='danger', comp='Button', comp_name='按钮', demo_name='危险按钮', cat='通用')]
        """
        for demo in self.demos:
            if demo.comp.comp != comp:
                continue
            yield demo

    def todo(self, file=None):
        """生成todo文件"""
        if file is None:
            file = r"doc\todo.md"

        if os.path.exists(file):
            with open(file, encoding="utf-8", mode="r") as fin:
                lines = fin.readlines()
        else:
            lines = []

        for demo in self.demos:
            demo_str = str(demo)
            if demo_str not in "\n".join(lines):
                lines.append(f"- [ ] {demo_str} - {demo.route_path}\n")

        with open(file, encoding="utf-8", mode="w") as fout:
            fout.writelines(lines)

    def comp_note(self, file=None):
        """生成组件笔记模板"""
        if file is None:
            file = r"doc\components.md"

        if os.path.exists(file):
            with open(file, encoding="utf-8", mode="r") as fin:
                lines = fin.readlines()
        else:
            lines = []

        for comp in self.iter_all_component():
            comp_str = f"{comp.comp}({comp.comp_name})"
            if comp_str not in "\n".join(lines):
                lines.append(f"## {comp_str}\n")

        with open(file, encoding="utf-8", mode="w") as fout:
            fout.writelines(lines)


class Routing:
    """路由"""

    def __init__(self, *, file=None, lines=None) -> None:
        if lines is not None:
            self._lines = list(lines)
        else:
            if file is None:
                file = r"src\app\app.routes.ts"
            self._file = file
            with open(file, mode="r", encoding="utf-8") as fin:
                self._lines = [line.rstrip() for line in fin.readlines()]

    def save(self, file=None):
        """保存文件"""
        if file is None:
            file = self._file

        if not file:
            raise ValueError("file 不能为空")

        with open(file, mode="w", encoding="utf-8") as fout:
            for line in self._lines:
                fout.write(line)
                fout.write("\n")

    def run(self, demos: DemoList):
        """执行添加路由"""
        for demo in demos.demos:
            self.add_route(demo)
        self.save()

    def has_demo(self, demo: DemoItem):
        """是否已包含demo组件"""
        return demo.demo_comp in "\n".join(self._lines)

    def add_demo_import(self, demo: DemoItem):
        """添加demo的import语句"""
        insert_index = self.find_last_import_index() + 1
        self._lines = (
            self._lines[:insert_index] + [demo.demo_import] + self._lines[insert_index:]
        )

    def add_demo_route_path(self, demo: DemoItem):
        """添加demo的路由配置"""
        insert_index = self.find_first_route_index()
        self._lines = (
            self._lines[:insert_index] + [demo.route] + self._lines[insert_index:]
        )

    def add_route(self, demo: DemoItem):
        """添加路由"""
        if self.has_demo(demo):
            return

        self.add_demo_import(demo)
        self.add_demo_route_path(demo)

    def add_routes(self, demos):
        """添加多个路由"""
        for demo in demos:
            self.add_route(demo)

    def find_last_import_index(self):
        """取得最后一个import语句

        >>> Routing(lines=['import { Routes } from "@angular/router";', \
            'import { WelcomeComponent } from "./pages/welcome/welcome.component";', \
            '', \
            'export const routes: Routes = [', \
            '  { path: "", pathMatch: "full", redirectTo: "/welcome" },', \
            '  { path: "welcome", component: WelcomeComponent },', '];']).find_last_import_index()
        1
        """
        imports = (
            index
            for index, line in enumerate(self._lines)
            if line.lstrip().startswith("import ")
        )
        return max(imports)

    def find_first_route_index(self):
        """取得第一个路由语句
        >>> Routing(lines=['import { Routes } from "@angular/router";', \
            'import { WelcomeComponent } from "./pages/welcome/welcome.component";', \
            '', \
            'export const routes: Routes = [', \
            '  { path: "", pathMatch: "full", redirectTo: "/welcome" },', \
            '  { path: "welcome", component: WelcomeComponent },', '];']).find_first_route_index()
        4
        """
        routing_lines = self._lines
        for index, line in enumerate(routing_lines):
            if line.lstrip().startswith("export const routes: Routes = ["):
                return index + 1
        raise ValueError("没找到路由配置语句")

    def __str__(self):
        return "\n".join(self._lines)

    def default_path(self, demo: DemoItem):
        """替换成默认路径"""
        for index, line in enumerate(self._lines):
            if line.strip().startswith("{ path: '', pathMatch: 'full'"):
                new_line = f"  {{ path: '', pathMatch: 'full', redirectTo: '/{demo.route_path}' }},"
                self._lines[index] = new_line
        self.save()


class Menu:
    """菜单"""

    def __init__(self, file=None) -> None:
        if file is None:
            file = r"src\app\app.component.html"
        self._file = file
        with open(file, encoding="utf-8") as fin:
            lines = fin.readlines()
            lines = [line.rstrip() for line in lines]
            self._lines = lines

    def save(self, file=None):
        """保存"""
        if file is None:
            file = self._file
        if not file:
            raise ValueError("file 不能为空")

        with open(file, mode="w", encoding="utf-8") as fout:
            for line in self._lines:
                fout.write(line)
                fout.write("\n")

    def create(self, demos: DemoList):
        """创建菜单"""
        for cat in demos.iter_category():
            yield " " * 6 + f'<div nz-submenu nzTitle="{cat}" nzIcon="dashboard">'
            for comp in demos.iter_component(cat):
                yield " " * 8 + f'<div nz-submenu nzTitle="{comp.comp} {comp.comp_name}">'
                for demo in demos.iter_demo(comp.comp):
                    yield " " * 10 + "<div nz-menu-item nzMatchRouter>"
                    yield " " * 10 + f'  <a routerLink="/{demo.route_path}">{demo.demo_name}</a>'
                    yield " " * 10 + "</div>"
                yield " " * 8 + "</div>"
            yield " " * 6 + "</div>"

    def reset_menu(self, demos: DemoList):
        """重置菜单"""

        def find_begin():
            for index, line in enumerate(self._lines):
                if line.strip() == "<!--menu begin-->":
                    return index + 1
            raise ValueError("没找到menu开始标志")

        def find_end():
            for index, line in enumerate(self._lines):
                if line.strip() == "<!--menu end-->":
                    return index
            raise ValueError("没找到menu结束标志")

        begin = find_begin()
        end = find_end()

        self._lines = self._lines[:begin] + list(self.create(demos)) + self._lines[end:]

    def open(self, comp: CompItem):
        """打开菜单"""
        self._lines = [line.replace("nzOpen", "") for line in self._lines]
        for index, line in enumerate(self._lines):
            if "nz-submenu" in line and (comp.cat in line):
                self._lines[index] = line[:-1] + " nzOpen>"
                break
        for index, line in enumerate(self._lines):
            if "nz-submenu" in line and comp.comp in line:
                self._lines[index] = line[:-1] + " nzOpen>"
                break
        self.save()

    def run(self, demos: DemoList, file=None):
        """执行"""
        self.reset_menu(demos)
        self.save(file)


def main():
    """主函数"""
    demo_list = DemoList()
    routing = Routing()
    routing.run(demo_list)
    Menu().run(demo_list)


def current(target: str):
    """把demo设置成当前项目"""
    demo = DemoList().find_demo(target)
    Menu().open(demo.comp)
    Routing().default_path(demo)


if __name__ == "__main__":
    # main()
    current("skeleton-demo-basic")
